
Alkaline.Modules.newModule('Analyzer', function() {

var TOKEN = Alkaline.JSInfo.TOKEN;
var tokenNames = Alkaline.JSInfo.tokenNames;
var exampleParser = new AlkalineParser();
this.opIsAssignment = exampleParser.opIsAssignment;
var precedence = exampleParser.precedence;
exampleParser = null;	// free the memory

var ProcessingState = Alkaline.Processing.ProcessingState;
var LexicalContext = Alkaline.Processing.LexicalContext;
var VariableDefinition = Alkaline.Processing.VariableDefinition;
var ReferencedValue = Alkaline.Processing.ReferencedValue;
var FunctionInstance = Alkaline.Processing.FunctionInstance;
var ValueNull = Alkaline.Processing.ValueNull;
var ValueUndefined = Alkaline.Processing.ValueUndefined;
var Operation = Alkaline.Processing.Operation;
var Condition = Alkaline.Processing.Condition;


/**
 *	run ( ast )
 */
this.run = function(ast) {
	this.builder = this.compileResult.treeBuilder;
	this.state = new ProcessingState(this.builder, ast);
	if (!ast.parent)
		this.state.save(ast);
	this.process(ast);
	this.state = null;
}


/**
 *	AST processing
 */
this.process = function(ast) {
	//if (ast.analyzed)
	//	return;
	this['process' + ast.token.type](ast);
	ast.analyzed = true;
}

this.processARRAY = function(ast) {
	if (ast.children && ast.children.length)
		for (var i = 0; i < ast.children.length; i++)
			this.process(ast.children[i]);
}

this.processArrayComprehension = function(ast) {
	this.state.save(ast.parent);
	this.process(ast.children[1]);
	this.process(ast.children[0]);
}

this.processBLOCK = function(ast) {
	this.state.save(ast);
	if (!ast.children || !ast.children.length)
		return;	// empty block; nothing to do
	var done = {};
	// first process function declarations
	for (var i = 0; i < ast.children.length; i++)
		if (ast.children[i].token.type == TOKEN.Function) {
			this.process(ast.children[i]);
			done[i] = true;
		}
	// then process the rest of instructions
	for (var i = 0; i < ast.children.length; i++)
		if (!done[i])
			this.process(ast.children[i]);
}

this.processBreak =
this.processContinue =
	function(ast) {
		if (ast.targetInstruction)
			// don't do this twice for the same loop
			return;
		ast.targetInstruction = null;
		this.jumpReference = null;
		var level = 0;
		var up = ast;
		var name = ast.children && ast.children.length && ast.children[0].token.text;
		var outOfFunction = false;
		var lastUsable = null;
		var errMsg = 'The label ' + (name ? '"' + name + '"' : '') + ' is invalid.';
		var errObj = name ? ast.children[0] : ast;
		while (up) {
			if ((up.token.type === TOKEN.Switch && ast.token.type === TOKEN.Break) || Alkaline.JSInfo.isLoop(up)) {
				if (!name) {
					ast.targetInstruction = up;
					return;
				}
				level++;
				lastUsable = up;
			} else if (up.token.type === TOKEN.Function) {
				this.error(1008, errMsg, errObj);
				return;
			} else if (up.token.type === TOKEN.LABEL && up.children[0].token.text === name) {
				if (up.children[1] !== lastUsable) {
					this.error(1008, errMsg, errObj);
					return;
				}
				ast.targetInstruction = lastUsable;
				if (level === 1)
					ast.deleteChild(0);
				else
					up.references++;
				return;
			}
			up = up.parent;
		}
		// no (valid) target was found
		this.error(1008, errMsg, errObj);
	}

this.processCALL = function(ast) {
	this.process(ast.children[0]);
	for (var i = 1; i < ast.children.length; i++)
		this.process(ast.children[i]);
}

this.processCase = function(ast) {
	this.process(ast.children[0]);
	this.process(ast.children[1]);
}

this.processCatch = function(ast) {
	this.state.pushContext(ast.newLexicalContext = new LexicalContext(this.state.currentContext, ast));
	var exception = ast.exceptionVariable = new VariableDefinition(ast.children[0].token.text, VariableDefinition.SOURCE_TYPE.IMPLIED, ast.children[0],
		new Alkaline.Processing.ReferencedValue(Alkaline.Processing.ReferencedValue.VALUE_TYPE.UNKNOWN, {}));
	this.state.currentContext.addVariable(exception);
	if (ast.children.length > 2)	// conditional catch
		this.process(ast.children[1]);
	this.process(ast.children[ast.children.length-1]);
	this.state.popContext();
}

this.processComment = function(ast) {
	// nothing to do here
}

this.processDefault = function(ast) {
	this.process(ast.children[0]);
}

this.processDefaultDirective = function(ast) {
	// [0] = "xml"
	// [1] = "namespace"
	this.process(ast.children[2]);
}

this.processDelete = function(ast) {
	this.process(ast.children[0]);
}

this.processDo = function(ast) {
	this.process(ast.children[0]);
	this.process(ast.children[1]);
}

this.processEMPTY = function(ast) {
	// nothing.
}

this.processEXPR = function(ast) {
	for (var i = 0; i < ast.children.length; i++)
		this.process(ast.children[i]);
}

this.processFalse = function(ast) {
	ast.objectValue = ReferencedValue.primitives.False;
}

this.processFILTER = function(ast) {
	this.process(ast.children[0]);
	this.process(ast.children[1]);
}

this.processFinally = function(ast) {
	this.process(ast.children[0]);
}

this.processFor = function(ast) {
	this.state.save(ast);
	var extra = ast.children[0].token.type == TOKEN.Each;
	if (ast.children.length - extra < 3) {
		//for-in
		this.process(ast.children[+extra]);
		this.process(ast.children[extra+1]);
	} else {
		// classic for with iteration
		for (var i = 0; i < ast.children.length; i++)
			this.process(ast.children[i]);
	}
}

this.processFunction = function(ast) {
	// children: [functionName,] arguments, function body
	if (!ast.objectValue) {
		var fun = new FunctionInstance(ast, this.state.currentContext);
		ast.objectValue = fun;
		this.state.pushContext(ast.objectValue.innerLexicalContext);
		// function name
		if (ast.children.length > 2) {
			var funVar = new VariableDefinition(ast.children[0].token.text, VariableDefinition.SOURCE_TYPE.IMPLIED, ast.children[0], fun);
			(ast.isFunctionDeclaration ? fun.innerLexicalContext.inheritedContext : fun.innerLexicalContext).addVariable(funVar);
			fun.functionNameVar = funVar;
			funVar.setValue(Condition.ALWAYS, fun);
		}
		// arguments
		var argDecl = ast.children[ast.children.length-2];
		if (argDecl.children && argDecl.children.length)
			for (var i = 0; i < argDecl.children.length; i++) {
				var varDef = new VariableDefinition(argDecl.children[i].token.text, VariableDefinition.SOURCE_TYPE.IMPLIED, argDecl.children[i],
					new Alkaline.Processing.ReferencedValue(Alkaline.Processing.ReferencedValue.VALUE_TYPE.UNKNOWN, {}));
				fun.innerLexicalContext.addVariable(varDef);
			}
	} else
		this.state.pushContext(ast.objectValue.innerLexicalContext);
	// body
	this.process(ast.children[ast.children.length-1]);
	this.state.popContext();
}

this.processId = function(ast) {
	var varRef = this.state.currentContext.getVariable(ast.token.text);
	if (!varRef) {
		varRef = new VariableDefinition(ast.token.text, VariableDefinition.SOURCE_TYPE.DETECTED);
		this.state.globalContext.addVariable(varRef);
	}
	if (ast.objectValue !== varRef) {
		varRef.addReference(ast);
		ast.objectValue = varRef;
	}
}

this.processIf = function(ast) {
	for (var i = 0; i < ast.children.length; i++)
		this.process(ast.children[i]);
}

this.processLABEL = function(ast) {
	// ast.children[0] is the label name
	ast.references = 0;
	this.process(ast.children[1]);
	if (!ast.references)
		this.builder.replaceTree(ast, ast.children[1]);
}

this.processNew = function(ast) {
	for (var i = 0; i < ast.children.length; i++)
		this.process(ast.children[i]);
}

this.processNull = function(ast) {
	ast.objectValue = ValueNull.instance;
}

this.processNumber = function(ast) {
	if (!ast.objectValue)
		ast.objectValue = new ReferencedValue(ReferencedValue.VALUE_TYPE.NUMBER, +ast.token.text);
}

this.processITEM = function(ast) {
	this.process(ast.children[0]);
}

this.processOBJECT = function(ast) {
	if (ast.children)
		for (var i = 0; i < ast.children.length; i++) {
			var child = ast.children[i];
			if (child.token.type === TOKEN.String)
				this.process(child.children[0]);
			else {
				// TODO: handle getters and setters later
				this.process(child.children[child.children.length-1]);
			}
		}
}

this.processEqual =
this.processIn =
this.processInstanceOf =
this.processOperator = function(ast) {
	this.process(ast.children[0]);
	this.process(ast.children[1]);
	if (ast.children.length > 2)
		this.process(ast.children[2]);
}

this.processPostOp = function(ast) {
	this.process(ast.children[0]);
}

this.processRegExp = function(ast) {
	var text = ast.token.text;
	var flagCnt = 0;
	while (text.charAt(text.length-1-flagCnt) !== '/')
		flagCnt++;
	ast.rxText = text.substr(1, text.length-flagCnt-3);	// remove first and last "/"
	ast.rxFlags = text.substr(text.length-flagCnt);
}

this.processReturn = function(ast) {
	if (ast.children && ast.children.length)
		this.process(ast.children[0]);
}

this.processSELECT = function(ast) {
	this.process(ast.children[0]);
	this.process(ast.children[1]);
}

this.processString = function(ast) {
	if (!ast.objectValue)
		ast.objectValue = new ReferencedValue(ReferencedValue.VALUE_TYPE.STRING, Alkaline.JSInfo.unescapeString(ast.token.text));
}

this.processSwitch = function(ast) {
	for (var i = 0; i < ast.children.length; i++)
		this.process(ast.children[i]);
}

this.processThrow = function(ast) {
	this.process(ast.children[0]);
}

this.processTrue = function(ast) {
	if (!ast.objectValue)
		ast.objectValue = ReferencedValue.primitives.True;
}

this.processTry = function(ast) {
	this.state.save(ast);
	for (var i = 0; i < ast.children.length; i++)
		this.process(ast.children[i]);
}

this.processTypeOf = function(ast) {
	this.process(ast.children[0]);
}

this.processUnaryOperator = function(ast) {
	this.process(ast.children[0]);
}

this.processVoid = function(ast) {
	this.process(ast.children[0]);
	this.objectValue = ValueUndefined.instance;
}

this.processWhile = function(ast) {
	this.process(ast.children[0]);
	this.process(ast.children[1]);
}

this.processYield = function(ast) {
	var fun = this.state.functionContexts.last().functionReference;
	fun.isGenerator = true;
	if (!fun.yields.contains(ast))
		fun.yields.push(ast);
	this.process(ast.children[0]);
}

//	variable definitions
this.processVar = function(ast) {
	this.collectVariableDefinitions(ast, this.state.functionContexts.last(), VariableDefinition.SOURCE_TYPE.DECLARED_VAR);
}

this.processLet = function(ast) {
	this.collectVariableDefinitions(ast, this.state.blockContexts.last(), VariableDefinition.SOURCE_TYPE.DECLARED_LET);
}

this.processConst = function(ast) {
	this.collectVariableDefinitions(ast, this.state.functionContexts.last(), VariableDefinition.SOURCE_TYPE.DECLARED_CONST);
}

this.collectVariableDefinitions = function(ast, context, sourceType) {
	if (ast.children)
		for (var i = 0; i < ast.children.length; i++) {
			var varName = ast.children[i];
			if (varName.token.type === TOKEN.Id) {
				if (!varName.objectValue)
					context.addVariable(varName.objectValue = new VariableDefinition(varName.token.text, sourceType, varName,
						// global variables may already exist and have a preset value (e.g. "window" in web browsers)
						context.inheritedContext ? null : new Alkaline.Processing.ReferencedValue(Alkaline.Processing.ReferencedValue.VALUE_TYPE.UNKNOWN, {})));
			} else
				this.compileResult.stdErr('Unknown var name type in decl: ' + varName);
		}
}


this._processNotImplemented = function(ast) {
	this.compileResult.stdWarn('[Analyzer] Processing of ' + Alkaline.JSInfo.tokenName[ast.token.type] + ' is not implemented: %o.', ast);
}

for (var i = 0; i < tokenNames.length; i++)
	this['process' + TOKEN[tokenNames[i]]] = this['process' + tokenNames[i]] || (this['process' + tokenNames[i]] = this._processNotImplemented);

});
